home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 08 - 1992 / 08.01 Apr⁄May 92 / Microsecond Timer / MicrosecondTimer.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-08-05  |  10.8 KB  |  452 lines  |  [TEXT/KAHL]

  1. /*                        MicrosecondTimer.c                */
  2. /*
  3.  * Time measurement.
  4.  * Copyright © 1991 Martin Minow.  All Rights Reserved
  5.  *
  6.  * This function creates a high-resolution "time of day"
  7.  * timer that is (or, at least, ought to be) synchronized
  8.  * with the system time of day value.  It uses the
  9.  * new time manager calls.
  10.  *
  11.  * In order to keep our timer in reasonable synchronization
  12.  * with the system time of day, we shadow that value at
  13.  * each time-of-day trap.
  14.  * 
  15.  * Usage:
  16.  *    InitializeMicrosecondTimer();
  17.  *        Call this -- once -- when your program starts. It
  18.  *        installs the timer interrupt routine. It returns
  19.  *        noErr if successful, or unimpErr if the Extended
  20.  *        Time Manager is not supported on this system.
  21.  *
  22.  *        Important: if you are using the Think C ANSI
  23.  *        and/or console library, be sure to call
  24.  *        InitializeMicrosecondTimer before calling any
  25.  *        stdio or console routines.  Otherwise, your program
  26.  *        may crash on exit under certain ill-defined
  27.  *        circumstances.
  28.  *
  29.  *    CancelMicrosecondTimer()
  30.  *        This must be called before your application exits.
  31.  *        InitializeMSecTimer() establishes an exit handeler
  32.  *        to force its call, so you needn't worry about it.
  33.  *
  34.  *    GetEpoch(MicrosecondEpoch *result)
  35.  *        Call this to get the time of day. The result
  36.  *        consists of a time (seconds) value that is
  37.  *        intended to track GetTimeOfDay exactly, extended
  38.  *        by the number of microseconds past this second.
  39.  *
  40.  *    DeltaTime(
  41.  *            MicrosecondEpoch        *startTime,
  42.  *            MicrosecondEpoch        *endTime
  43.  *            signed long                *difference
  44.  *        )
  45.  *        Compute the difference between two extended
  46.  *        time values, returning the result in the third
  47.  *        parameter (as a signed number of microseconds).
  48.  *        The result will be positive if time2 is later
  49.  *        than time1. DeltaTime returns TRUE if the
  50.  *        absolute value of the difference (in seconds)
  51.  *        is less than 35 minutes (a signed longword can
  52.  *        resolve a 34 minute interval). (You might want
  53.  *        to redo this to return a double-precision format
  54.  *        value, rather than a longword.)
  55.  *        
  56.  *    EpochToString(
  57.  *            MicrosecondEpoch        *epoch
  58.  *            Str255                    result
  59.  *        )
  60.  *        Convert an extended time value to a fixed-width,
  61.  *        fixed-format Pascal string "hh:mm:ss.fraction".
  62.  *
  63.  * Although the code has not been tested under MPW, it
  64.  * ought to port easily: just re-do the asm stuff.
  65.  *
  66.  * Acknowledgements:
  67.  *    Parts of the time manager calls are based on a
  68.  *    timing module in the MacDTS FracApp demo program
  69.  *    by Keith Rollin an Bo3b Johnson.
  70.  *
  71.  *    The exit handler is based on similar code in the
  72.  *    atexit() function in the Think C support library.
  73.  */
  74.  
  75. #include <GestaltEqu.h>
  76. #ifndef THINK_C
  77. #include <SysEqu.h>
  78. #endif
  79. #include <Timer.h>
  80. #include <Traps.h>
  81. #include "MicrosecondTimer.h"
  82.  
  83. /*
  84.  * This is needed to establish an exit trap handler.
  85.  */
  86. typedef void        (*MyProcPtr)(void);
  87. typedef struct {
  88.     short            jmp;
  89.     MyProcPtr        function;
  90. } JumpVector;
  91.  
  92. static void            *oldExitVector;
  93. static JumpVector    *jumpVector;
  94. static void            TimerExitHandler(void);
  95.  
  96. #define MILLION        (1000000L)
  97.  
  98. /*
  99.  * This is a time manager record, extended to include a
  100.  * shadow copy of the system time of day value that is
  101.  * updated once a second.
  102.  */
  103. typedef struct TimeInfoRecord {
  104.     TMTask            TMTask;            /* The task record    */
  105.     unsigned long    epoch;            /* Time of day info    */
  106. } TimeInfoRecord, *TimeInfoPtr;
  107.  
  108. static TimeInfoRecord    gTimeInfo;
  109. #define TIME            (gTimeInfo.TMTask)
  110. static long                gOverheadTime;
  111. static pascal void        TimeCounter(void);
  112.  
  113. static void Concat(StringPtr dst, StringPtr src);
  114.  
  115. /*
  116.  * Install a timer interrupt procedure.
  117.  */
  118. OSErr
  119. InitializeMicrosecondTimer()
  120. {
  121.         long            timeOfDay;
  122.         long            gestaltResult;
  123.         OSErr            status;
  124.         
  125.         if (TIME.tmAddr != NULL)
  126.             status = noErr;        /* Already installed    */
  127.         else {
  128.             status = Gestalt(
  129.                     gestaltTimeMgrVersion,
  130.                     &gestaltResult
  131.                 );
  132.             if (status == noErr
  133.              && gestaltResult < gestaltExtendedTimeMgr)
  134.                 status = unimpErr;
  135.             if (status == noErr) {
  136.                 /*
  137.                  * Install a trap handler for ExitToShell
  138.                  */
  139.                 oldExitVector =
  140.                     (void *) GetTrapAddress(_ExitToShell);
  141.                 if (ROM85 >= 0) {
  142.                     SetTrapAddress(
  143.                         (long) TimerExitHandler,
  144.                         _ExitToShell
  145.                     );
  146.                 }
  147.                 else {
  148.                     /*
  149.                      * Install a trap handler
  150.                      * in the system heap.
  151.                      */
  152.                     jumpVector = (JumpVector *)
  153.                         NewPtrSys(sizeof (JumpVector));
  154.                     if (jumpVector == NULL) {
  155.                         status = memFullErr;
  156.                         goto exit;
  157.                     }
  158.                     else {
  159.                         jumpVector->jmp = 0x4EF9;
  160.                         jumpVector->function =
  161.                             TimerExitHandler;
  162.                         SetTrapAddress(
  163.                             (long) jumpVector,
  164.                             _ExitToShell
  165.                         );
  166.                     }
  167.  
  168.                 }
  169.                 /*
  170.                  * Install the time manager task and
  171.                  * start it rolling.
  172.                  */
  173.                 TIME.tmAddr = (ProcPtr) TimeCounter;
  174.                 InsXTime(&TIME);
  175.                 /*
  176.                  * Align our timer to the system's
  177.                  */
  178.                 timeOfDay = Time;
  179.                 do {
  180.                     gTimeInfo.epoch = Time;
  181.                 } while (timeOfDay == gTimeInfo.epoch);
  182.                 /*
  183.                  * We should really do this a bunch
  184.                  * of times and take the minimum.
  185.                  * gOverheadTime measures the amount
  186.                  * of time the PrimeTime/RmvTime sequence
  187.                  * requires, See the discussion in IM-VI.
  188.                  */
  189.                 PrimeTime(&TIME, -MILLION);
  190.                 RmvTime(&TIME);
  191.                 gOverheadTime = MILLION + TIME.tmCount;
  192.                 /*
  193.                  * Restart the timer
  194.                  */
  195.                 InsXTime(&TIME);
  196.                 PrimeTime(&TIME, 0);
  197.             }
  198.         }
  199. exit:    return (status);
  200. }
  201.  
  202. /*
  203.  * GetEpoch returns the current extended time of day.
  204.  * It requires the drift-free time manager. See the
  205.  * Time Manager discussion in Inside Mac VI for details
  206.  * of the procedure.
  207.  */
  208. void
  209. GetEpoch(
  210.         MicrosecondEpochPtr        result
  211.     )
  212. {
  213.         RmvTime(&TIME);                    /* Stop Clock    */
  214.         result->time = gTimeInfo.epoch;    /* Get seconds    */
  215.         /*
  216.          * TIME.tmCount contains the residual number of
  217.          * microseconds. This is a negative number (see
  218.          * IM-VI). The following, then, computes the
  219.          * number of microseconds that have elapsed in
  220.          * the current second.
  221.          */
  222.         result->microsecond = 
  223.             (MILLION + TIME.tmCount)    /* Offset "now"    */
  224.             - gOverheadTime;            /* - call cost    */
  225.         if (result->microsecond < 0) {    /* New second?    */
  226.             --result->time;                /* Correct it.    */
  227.             result->microsecond += MILLION;
  228.         }
  229.         InsXTime(&TIME);                /* Drift-free    */
  230.         PrimeTime(&TIME, 0);            /* Timer start    */
  231. }
  232.  
  233. /*
  234.  * Return the difference between two (nearby) epochs.
  235.  * The result is in microseconds and has a range of
  236.  * up to about 35 minutes.
  237.  *
  238.  * DeltaTime returns TRUE if deltaTime is valid.
  239.  */
  240. Boolean
  241. DeltaTime(
  242.         MicrosecondEpochPtr        epoch1,
  243.         MicrosecondEpochPtr        epoch2,
  244.         signed long                *deltaTime
  245.     )
  246. {
  247.         long                    seconds;
  248.         long                    microseconds;
  249.         
  250.         seconds = epoch2->time - epoch1->time;
  251.         microseconds =
  252.             epoch2->microsecond - epoch1->microsecond;
  253.         *deltaTime = (seconds * MILLION) + microseconds;
  254.         /*
  255.          * The result is valid only if the
  256.          * absolute value of the difference is
  257.          * less than about 35 minutes.  I.e.
  258.          *    2^31 <= (35 * 60 * 10^6)
  259.          */
  260.         if (seconds < 0)
  261.             seconds = (-seconds);
  262.         return (seconds <= (34 * 60));
  263. }
  264.  
  265. /*
  266.  * This local function formats hour:minute:second.
  267.  */
  268. static void
  269. FormatTimeString(
  270.         StringPtr        result,
  271.         long            what,
  272.         Boolean            needColon
  273.     )
  274. {
  275.         Str255            value;
  276.         
  277.         if (needColon)
  278.             result[++result[0]] = ':';
  279.         NumToString(what, value);
  280.         if (value[0] == 1)
  281.             result[++result[0]] = '0';
  282.         Concat(result, value);
  283. }
  284.  
  285. /*
  286.  * Convert the time of day to a consistent, fixed-width
  287.  * format of hh:mm:ss.microseconds. This is always in
  288.  * 24 hour format.
  289.  */
  290. void
  291. EpochToString(
  292.         MicrosecondEpochPtr        epochPtr,
  293.         StringPtr                result
  294.     )
  295. {
  296.         unsigned int            i;
  297.         DateTimeRec                now;
  298.         Str255                    value;
  299.  
  300.         Secs2Date(epochPtr->time, &now);
  301.         result[0] = 0;
  302.         FormatTimeString(result, now.hour,   FALSE);
  303.         FormatTimeString(result, now.minute, TRUE);
  304.         FormatTimeString(result, now.second, TRUE);
  305.         NumToString(
  306.             epochPtr->microsecond + MILLION,
  307.             value
  308.         );
  309.         value[1] = '.';
  310.         Concat(result, value);
  311. }
  312.  
  313. /*
  314.  * String concatenator for Pascal strings.
  315.  */
  316. static void
  317. Concat(
  318.         StringPtr        dst,
  319.         StringPtr        src
  320.     )
  321. {
  322.         short            copySize;
  323.         
  324.         copySize = src[0];
  325.         if ((copySize + dst[0]) > 255)
  326.             copySize = 255 - dst[0];
  327.         BlockMove(
  328.             &src[1],
  329.             &dst[dst[0] + 1],
  330.             (long) copySize
  331.         );
  332.         dst[0] += copySize;
  333. }
  334.  
  335. /*
  336.  * Adjust the clock by adding the adjustment to the
  337.  * current clock.  There is a built-in delay to
  338.  * make sure our timer task gets to do its thing.
  339.  *
  340.  * Note: the right way to do this is to change the system
  341.  * clock tick base from 1000000 and continually adjust
  342.  * the clock a bit every second until it's right.
  343.  * Unfortunately, we don't have access to the system
  344.  * clock time manager record.
  345.  */
  346. void
  347. AdjustClock(
  348.         long            adjustment
  349.     )
  350. {
  351.         MicrosecondEpoch    ourEpoch;
  352.         long                timeOfDay;
  353.  
  354.         GetEpoch(&ourEpoch);
  355.         ourEpoch.time += (adjustment / MILLION);
  356.         adjustment %= MILLION;
  357.         if (ourEpoch.microsecond >= (MILLION / 2))
  358.             ++ourEpoch.time;
  359.         else if (ourEpoch.microsecond <= (-(MILLION / 2)))
  360.             --ourEpoch.time;
  361.         SetDateTime(ourEpoch.time);
  362.         /*
  363.          * Vamp until our shadow clock has a chance to
  364.          * update the local value.
  365.          */
  366.         GetEpoch(&ourEpoch);
  367.         timeOfDay = ourEpoch.time;
  368.         do {
  369.             GetEpoch(&ourEpoch);
  370.         } while (timeOfDay == ourEpoch.time);
  371. }
  372.  
  373. /*
  374.  * This will be called automatically by the
  375.  * ExitToShell trap.
  376.  */
  377. void
  378. CancelMicrosecondTimer()
  379. {
  380.  
  381. #if 0    /* Enable this to put a debug trap here            */
  382.         asm {
  383.             nop
  384.         }
  385. #endif
  386.         if (TIME.tmAddr != NULL) {
  387.             RmvTime(&TIME);
  388.             TIME.tmAddr = NULL;
  389.         }
  390.         if (oldExitVector != NULL) {
  391.             SetTrapAddress(
  392.                 (long) oldExitVector,
  393.                 _ExitToShell
  394.             );
  395.             oldExitVector = NULL;
  396.             if (jumpVector != NULL) {
  397.                 DisposPtr(jumpVector);
  398.                 jumpVector = NULL;
  399.             }
  400.         }
  401. }
  402.  
  403. /*
  404.  * This is called by the ExitToShell trap.
  405.  * It cancels the timer service and removes
  406.  * itself from the trap process, then re-calls
  407.  * ExitToShell to allow other trap handlers to
  408.  * execute.
  409.  */
  410. static void
  411. TimerExitHandler()
  412. {
  413.         long            oldA5 = SetCurrentA5();
  414.  
  415.         CancelMicrosecondTimer();
  416.         SetA5(oldA5);
  417.         ExitToShell();        /* Call next exit handler    */
  418. }
  419.  
  420. /*
  421.  * This private routine is called by the TimeManager at
  422.  * every clock tick. There is blood on every line of this
  423.  * function -- and on a number of lines of code that aren't
  424.  * here any more. This function will need to be rewritten
  425.  * for MPW-C, as that compiler lacks an asm statement.
  426.  */
  427. static pascal void
  428. TimeCounter()
  429. {
  430.         asm {
  431.             /*
  432.              * When we are called, A1 -> the time info
  433.              * record which we have extended with our
  434.              * "time of day" shadow. Update it with the
  435.              * current system time-of-day value (so that
  436.              * we remain coordinated with any changes
  437.              * caused by SetDateTime or Control Panel
  438.              * calls). This may mean that we are up to
  439.              * one second out of step from the system, but
  440.              * this probably can't be helped.  TimeLM is
  441.              * the system global "time of day" variable.
  442.              * This variable has a different name in MPW-C.
  443.              */
  444.             move.l    TimeLM,    \
  445.                         OFFSET(TimeInfoRecord,epoch)(a1)
  446.             move.l    a1,a0            ;; a0 = TmTaskPtr 
  447.             move.l    #-MILLION,d0    ;; d0 = count
  448.             dc.w    0xA05A            ;; _PrimeTime
  449.         }
  450. }
  451.  
  452.